﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Perspective.Wpf3D.Primitives;
using Perspective.Wpf3D.Sculptors;

namespace Perspective.Wpf3D.Controls
{
    /// <summary>
    /// A 3D box element with thickness
    /// 
    /// </summary>
    public class ThicknessBox3D : ContentControl3D
    {
        #region Contants
        private const double MINTHICKNESS = 0.01;
        private const double MAXTHICKNESS = 100;
        private static Material defaultMaterial;
        #endregion

        #region Constructors

        static ThicknessBox3D()
        {
            ContentProperty.OverrideMetadata(
               typeof(ThicknessBox3D),
               new UIPropertyMetadata(ContentPropertyChanged));

            SolidColorBrush solidColorBrush = new SolidColorBrush();
            // solidColorBrush.Color = Colors.Goldenrod;
            // solidColorBrush.Color = Colors.LightGray;
            // solidColorBrush.Opacity = 1.0;
            solidColorBrush.Color = Color.FromArgb(0xFF, 0xFF, 0xDE, 0x46);

            defaultMaterial = new DiffuseMaterial(solidColorBrush);

            FrontMaterialProperty = DependencyProperty.Register(
              "FrontMaterial",
              typeof(Material),
              typeof(ThicknessBox3D),
              new PropertyMetadata(defaultMaterial, VisualPropertyChanged));

            BackMaterialProperty = DependencyProperty.Register(
             "BackMaterial",
             typeof(Material),
             typeof(ThicknessBox3D),
             new PropertyMetadata(defaultMaterial, VisualPropertyChanged));

            LeftMaterialProperty = DependencyProperty.Register(
             "LeftMaterial",
             typeof(Material),
             typeof(ThicknessBox3D),
             new PropertyMetadata(defaultMaterial, VisualPropertyChanged));

            RightMaterialProperty = DependencyProperty.Register(
             "RightMaterial",
             typeof(Material),
             typeof(ThicknessBox3D),
             new PropertyMetadata(defaultMaterial, VisualPropertyChanged));

            UpMaterialProperty = DependencyProperty.Register(
             "UpMaterial",
             typeof(Material),
             typeof(ThicknessBox3D),
             new PropertyMetadata(defaultMaterial, VisualPropertyChanged));

            DownMaterialProperty = DependencyProperty.Register(
             "DownMaterial",
             typeof(Material),
             typeof(ThicknessBox3D),
             new PropertyMetadata(defaultMaterial, VisualPropertyChanged));

            DefaultMaterialProperty = DependencyProperty.Register(
               "DefaultMaterial",
               typeof(Material),
               typeof(ThicknessBox3D),
               new PropertyMetadata(defaultMaterial, DefaultMaterialPropertyChanged));
        }

        /// <summary>
        /// Callback called when the Content property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void ContentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ThicknessBox3D g = (ThicknessBox3D)d;
           
        }

        #endregion

        #region Properties

        #region Length  长度 X轴宽度

        /// <summary>
        /// Gets or sets the Length possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double Length
        {
            get { return (double)GetValue(LengthProperty); }
            set { SetValue(LengthProperty, value); }
        }

        /// <summary>
        /// Identifies the Length dependency property.
        /// </summary>
        public static readonly DependencyProperty LengthProperty =
            DependencyProperty.Register(
                "Length",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    1.0,
                    LengthPropertyChanged,
                    LengthCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the Length property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void LengthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object LengthCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }

        #endregion

        #region Width 宽度 Z宽度
        /// <summary>
        /// Gets or sets the Width possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double Width
        {
            get { return (double)GetValue(WidthProperty); }
            set { SetValue(WidthProperty, value); }
        }

        /// <summary>
        /// Identifies the Width dependency property.
        /// </summary>
        public static readonly DependencyProperty WidthProperty =
            DependencyProperty.Register(
                "Width",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    1.0,
                    WidthPropertyChanged,
                    WidthCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the Width property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void WidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object WidthCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region Height 高度 Y宽度
        /// <summary>
        /// Gets or sets the Height possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double Height
        {
            get { return (double)GetValue(HeightProperty); }
            set { SetValue(HeightProperty, value); }
        }

        /// <summary>
        /// Identifies the Height dependency property.
        /// </summary>
        public static readonly DependencyProperty HeightProperty =
            DependencyProperty.Register(
                "Height",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    1.0,
                    HeightPropertyChanged,
                    HeightCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the Height property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void HeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object HeightCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region FrontThickness 正面厚度
         /// <summary>
        /// Gets or sets the FrontThickness possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double FrontThickness
        {
            get { return (double)GetValue(FrontThicknessProperty); }
            set { SetValue(FrontThicknessProperty, value); }
        }

        /// <summary>
        /// Identifies the FrontThickness dependency property.
        /// </summary>
        public static readonly DependencyProperty FrontThicknessProperty =
            DependencyProperty.Register(
                "FrontThickness",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    0.1,
                    FrontThicknessPropertyChanged,
                    FrontThicknessCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the FrontThickness property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void FrontThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object FrontThicknessCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region BackThickness 背面厚度
        /// <summary>
        /// Gets or sets the BackThickness possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double BackThickness
        {
            get { return (double)GetValue(BackThicknessProperty); }
            set { SetValue(BackThicknessProperty, value); }
        }

        /// <summary>
        /// Identifies the BackThickness dependency property.
        /// </summary>
        public static readonly DependencyProperty BackThicknessProperty =
            DependencyProperty.Register(
                "BackThickness",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    0.1,
                    BackThicknessPropertyChanged,
                    BackThicknessCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the BackThickness property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void BackThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object BackThicknessCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region LeftThickness 左面厚度
         /// <summary>
        /// Gets or sets the LeftThickness possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double LeftThickness
        {
            get { return (double)GetValue(LeftThicknessProperty); }
            set { SetValue(LeftThicknessProperty, value); }
        }

        /// <summary>
        /// Identifies the LeftThickness dependency property.
        /// </summary>
        public static readonly DependencyProperty LeftThicknessProperty =
            DependencyProperty.Register(
                "LeftThickness",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    0.1,
                    LeftThicknessPropertyChanged,
                    LeftThicknessCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the LeftThickness property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void LeftThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object LeftThicknessCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region RightThickness 右面厚度
         /// <summary>
        /// Gets or sets the RightThickness possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double RightThickness
        {
            get { return (double)GetValue(RightThicknessProperty); }
            set { SetValue(RightThicknessProperty, value); }
        }

        /// <summary>
        /// Identifies the RightThickness dependency property.
        /// </summary>
        public static readonly DependencyProperty RightThicknessProperty =
            DependencyProperty.Register(
                "RightThickness",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    0.1,
                    RightThicknessPropertyChanged,
                    RightThicknessCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the RightThickness property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void RightThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object RightThicknessCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region UpThickness 上面厚度
         /// <summary>
        /// Gets or sets the UpThickness possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double UpThickness
        {
            get { return (double)GetValue(UpThicknessProperty); }
            set { SetValue(UpThicknessProperty, value); }
        }

        /// <summary>
        /// Identifies the UpThickness dependency property.
        /// </summary>
        public static readonly DependencyProperty UpThicknessProperty =
            DependencyProperty.Register(
                "UpThickness",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    0.1,
                    UpThicknessPropertyChanged,
                    UpThicknessCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the UpThickness property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void UpThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object UpThicknessCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region DownThickness 下面厚度
         /// <summary>
        /// Gets or sets the DownThickness possible Value of the ThicknessBox element. 
        /// This is a dependency property.
        /// </summary>
        public double DownThickness
        {
            get { return (double)GetValue(DownThicknessProperty); }
            set { SetValue(DownThicknessProperty, value); }
        }

        /// <summary>
        /// Identifies the DownThickness dependency property.
        /// </summary>
        public static readonly DependencyProperty DownThicknessProperty =
            DependencyProperty.Register(
                "DownThickness",
                typeof(double),
                typeof(ThicknessBox3D),
                new PropertyMetadata(
                    0.1,
                    DownThicknessPropertyChanged,
                    DownThicknessCoerceValue),
                    DoubleValidateValue);

        /// <summary>
        /// Callback called when the DownThickness property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void DownThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //d.InvalidateProperty(RateProperty);
            //d.InvalidateProperty(ValueProperty);
        }

        private static object DownThicknessCoerceValue(DependencyObject d, object value)
        {
            if (((double)value) < MINTHICKNESS)
            {
                return MINTHICKNESS;
            }
            else if (((double)value) > MAXTHICKNESS)
            {
                return MAXTHICKNESS;
            }
            return value;
        }
        #endregion

        #region DefaultMaterial 默认材质
        /// <summary>
        /// Gets or sets the element's material.
        /// A default material is provided.
        /// </summary>
        public Material DefaultMaterial
        {
            get { return (Material)GetValue(DefaultMaterialProperty); }
            set { SetValue(DefaultMaterialProperty, value); }
        }

        /// <summary>
        /// Identifies the DefaultMaterial dependency property.
        /// </summary>
        public static readonly DependencyProperty DefaultMaterialProperty;

        /// <summary>
        /// Callback called when the DefaultMaterial property's value has changed.
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        private static void DefaultMaterialPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ThicknessBox3D thicknessBox = (ThicknessBox3D)d;
            if (thicknessBox.FrontMaterial == defaultMaterial)
            {
                thicknessBox.FrontMaterial = (Material)(e.NewValue);
            }
            if (thicknessBox.BackMaterial == defaultMaterial)
            {
                thicknessBox.BackMaterial = (Material)(e.NewValue);
            }
            if (thicknessBox.LeftMaterial == defaultMaterial)
            {
                thicknessBox.LeftMaterial = (Material)(e.NewValue);
            }
            if (thicknessBox.RightMaterial == defaultMaterial)
            {
                thicknessBox.RightMaterial = (Material)(e.NewValue);
            }
            if (thicknessBox.UpMaterial == defaultMaterial)
            {
                thicknessBox.UpMaterial = (Material)(e.NewValue);
            }
            if (thicknessBox.DownMaterial == defaultMaterial)
            {
                thicknessBox.DownMaterial = (Material)(e.NewValue);
            }
        }
        #endregion

        #region FrontMaterial 正面材质
        /// <summary>
        /// Gets or sets the element's FrontMaterial.
        /// A default FrontMaterial is provided.
        /// </summary>
        public Material FrontMaterial
        {
            get { return (Material)GetValue(FrontMaterialProperty); }
            set { SetValue(FrontMaterialProperty, value); }
        }

        /// <summary>
        /// Identifies the FrontMaterial dependency property.
        /// </summary>
        public static readonly DependencyProperty FrontMaterialProperty;

        #endregion

        #region BackMaterial 背面材质
        /// <summary>
        /// Gets or sets the element's BackMaterial.
        /// A default BackMaterial is provided.
        /// </summary>
        public Material BackMaterial
        {
            get { return (Material)GetValue(BackMaterialProperty); }
            set { SetValue(BackMaterialProperty, value); }
        }

        /// <summary>
        /// Identifies the BackMaterial dependency property.
        /// </summary>
        public static readonly DependencyProperty BackMaterialProperty;

        #endregion

        #region LeftMaterial 左面材质
        /// <summary>
        /// Gets or sets the element's LeftMaterial.
        /// A default LeftMaterial is provided.
        /// </summary>
        public Material LeftMaterial
        {
            get { return (Material)GetValue(LeftMaterialProperty); }
            set { SetValue(LeftMaterialProperty, value); }
        }

        /// <summary>
        /// Identifies the LeftMaterial dependency property.
        /// </summary>
        public static readonly DependencyProperty LeftMaterialProperty;

        #endregion

        #region RightMaterial 右面材质
        /// <summary>
        /// Gets or sets the element's RightMaterial.
        /// A default RightMaterial is provided.
        /// </summary>
        public Material RightMaterial
        {
            get { return (Material)GetValue(RightMaterialProperty); }
            set { SetValue(RightMaterialProperty, value); }
        }

        /// <summary>
        /// Identifies the RightMaterial dependency property.
        /// </summary>
        public static readonly DependencyProperty RightMaterialProperty;

        #endregion

        #region UpMaterial 上面材质
        /// <summary>
        /// Gets or sets the element's UpMaterial.
        /// A default UpMaterial is provided.
        /// </summary>
        public Material UpMaterial
        {
            get { return (Material)GetValue(UpMaterialProperty); }
            set { SetValue(UpMaterialProperty, value); }
        }

        /// <summary>
        /// Identifies the UpMaterial dependency property.
        /// </summary>
        public static readonly DependencyProperty UpMaterialProperty;

        #endregion

        #region DownMaterial 下面材质
        /// <summary>
        /// Gets or sets the element's DownMaterial.
        /// A default DownMaterial is provided.
        /// </summary>
        public Material DownMaterial
        {
            get { return (Material)GetValue(DownMaterialProperty); }
            set { SetValue(DownMaterialProperty, value); }
        }

        /// <summary>
        /// Identifies the DownMaterial dependency property.
        /// </summary>
        public static readonly DependencyProperty DownMaterialProperty;

        #endregion

        #region VisibleSides 可见的面
        /// <summary>
        /// Gets or sets the sides of the model.
        /// Default is BoxSides.All
        /// XAML usage : VisibleSides="Back,Left,Right,Up,Down"
        /// </summary>
        public BoxSides VisibleSides
        {
            get { return (BoxSides)GetValue(VisibleSidesProperty); }
            set { SetValue(VisibleSidesProperty, value); }
        }

        /// <summary>
        /// Identifies the VisibleSides dependency property.
        /// </summary>
        public static readonly DependencyProperty VisibleSidesProperty =
            DependencyProperty.Register(
                "VisibleSides",
                typeof(BoxSides),
                typeof(ThicknessBox3D),
                new UIPropertyMetadata(
                    BoxSides.All));

        #endregion

        #endregion

        #region Function
        /// <summary>
        /// Callback to call in subclasses when a visual dependency property value has changed (i.e. by databinding).
        /// </summary>
        /// <param name="d">Sender object</param>
        /// <param name="e">Callback arguments</param>
        protected static void VisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ContentControl3D element = (ContentControl3D)d;
            element.InvalidateModel();
        }

        /// <summary>
        /// 检测Double
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static bool DoubleValidateValue(object value)
        {
            double d = (double)value;
            if (!double.IsNaN(d))
            {
                return !double.IsInfinity(d);
            }
            return false;
        }

        /// <summary>
        /// Gets the name (without path and extension) of the XAML file that describes the appearence of the control.
        /// </summary>
        protected override string ContentFileName
        {
            get
            {
                return "ThicknessBox3D";
            }
        }
        #endregion
       
    }
}
